/*
 * Created by SharpDevelop.
 * User: Alice
 * Date: 27.02.2012
 * Time: 18:01
 * 
 * To change this template use Tools | Options | Coding | Edit Standard Headers.
 */
using System;
using System.Net;
using System.IO;
using System.Threading;
using System.Net.Sockets;
using System.Collections;
using System.Text;
using System.Timers;

namespace monoServer
{
	class Server
	{
		private Queue sessionQueue;
		private Logger l;
		private DataBase db;
		private Configuration c;
		private DataManager dm;
		//private Hashtable sessionTable;
		//private ConnectionManager connectionManager;
		//private Chat chat;
		private Error error;
		private TcpListener listener;
		private Cron cron;
		private SessionManager sm;
		private ThreadManager tm;
		private ulong mainThreadID;
		private ulong adminThreadID;
		private BugTable testtable;
		
		
		public static void Main(string[] args)
		{
			//Configuration c = new Configuration();
			//Logger l = new Logger(c);
			
			Server s = null;
			//l.writeInfo(Logger.FILE+Logger.TERMINAL, "Initialisiere BugServer...");
			Console.WriteLine("Initialisiere BugServer");
			try
			{
				s = new Server();
				s.startMainThread();
			} catch (Exception ex) {
				//l.writeError(Logger.FILE+Logger.TERMINAL, ex.Message);
				Console.WriteLine(ex.Message);
			}
			//l.writeInfo(Logger.FILE+Logger.TERMINAL, "BugServer erfolgreich Initialisiert");
			//l.writeInfo(l.BOTH, "Game-Server successfully created!");
			//l.writeInfo(Logger.FILE+Logger.TERMINAL, "Versuche BugServer zu starten...");
			/*
			try {
			} catch (Exception ex) {
				l.writeError(Logger.FILE+Logger.TERMINAL, ex.Message);
			}
			*/
		}
		
		public void startMainThread()
		{
			this.mainThreadID = this.tm.getNewThread(new ThreadStart(this.start));
			this.tm.startThread(this.mainThreadID);
		}
		
		public void start()
		{
			this.listener.Start();
			
			this.l.writeInfo(Logger.FILE+Logger.TERMINAL, "Herzlichen Glückwunsch! BugServer erfolgreich gestartet.");
			while(true)
			{	
				Session s = new Session(this.dm.currentSessionKey);
				s.com = new CommunicatorParallel(this.listener.AcceptTcpClient(), this.dm, this.l);
				this.sessionQueue.Enqueue(s);
				ulong tid = this.tm.getNewThread(new ThreadStart(this.clientThread));
				this.tm.startThread(tid);
			}
		}
		
		public Server()
		{
			// This is a testing bugtable
			this.testtable = new BugTable("testtable", true);
			this.testtable.addColumn("col1", typeof(string));
			this.testtable.addColumn("col2", typeof(string));
			this.testtable.addColumn("col3", typeof(string));
			
			//this.sessionTable = new Hashtable();
			this.sm = new SessionManager();
			
			//Thread t = new Thread(this.adminThread);
			//t.Start();
			this.c = new Configuration("/etc/monoserver");
			this.l = new Logger(c);
			//ThreadPool.QueueUserWorkItem(adminThread);
			
			this.sessionQueue = new Queue();
			
			// Datenbank initialisieren
			this.l.writeInfo(Logger.FILE+Logger.TERMINAL, "Versuche Datenbank-Pool zu erzeugen...");
			this.db = new DataBase(this.l, new User(0,(string)this.c.values["db_user"],(string)this.c.values["db_pw"]), (string)this.c.values["db_ip"], (string)this.c.values["db_name"],int.Parse((string)this.c.values["db_con_poolsize"]));
			this.l.writeInfo(Logger.FILE+Logger.TERMINAL, "Datenbank-Pool erfolgreich Initialisiert");
			
			// Starte Data-Manager
			this.l.writeInfo(Logger.FILE+Logger.TERMINAL, "Starte Data-Manager...");
			this.dm = new DataManager(int.Parse((string)this.c.values["session_key_start_value"]), ulong.Parse((string)this.c.values["telegram_key_start_value"]));
			this.l.writeInfo(Logger.FILE+Logger.TERMINAL, "Data-Manager erfolgreich gestartet");
			
			// Initialiere Chat
			//this.chat = new Chat(this.db, this.dm, this.c.loadChatMessageAtStart, this.c.intervalRemoveOldChatMessages);

			// Setting Up Cron-Server
			this.l.writeInfo(Logger.FILE+Logger.TERMINAL, "Try to Start the Cron-Server...");
			this.cron = new Cron();
			this.cron.addJob("serveralive", new ElapsedEventHandler(this.l.writeServerAliveToLog),int.Parse((string)this.c.values["alive_message_cron_time"]));
			this.cron.addJob("checkping",new ElapsedEventHandler(this.checkPing),int.Parse ((string)this.c.values["check_ping_time"]));
			this.cron.addJob("setsessioncounternull", new ElapsedEventHandler(this.dm.setSessionKeyToNull), 60*60*24);
			//cronServer.addJob("removeoldchatmessages", new ElapsedEventHandler(this.chat.removeOldMessages), 60*this.c.intervalRemoveOldChatMessages);
			this.cron.startAllJobs();
			this.l.writeInfo(Logger.FILE+Logger.TERMINAL, "Cron-Server successfully started");
			
			// Setting Up Threading-Pool
			this.mainThreadID = 0;
			this.tm = new ThreadManager(10, this.dm);
			this.adminThreadID = this.tm.getNewThread(new ThreadStart(this.adminThread));
			this.tm.startThread(this.adminThreadID);
		 	
			// Setting Up Listener
			this.listener = new TcpListener(IPAddress.Parse((string)this.c.values["local_ip"]),int.Parse((string)this.c.values["local_port"]));
			
			// Wird benötigt um Error-Objekten ohne errortext anhand der error# einen text per merge zuzuweisen
			this.error = new Error();
		}
		
		public void checkPing(object sender, ElapsedEventArgs e)
		{
			// TODO checkPing komplett überarbeiten
			//this.l.writeDebug(Logger.FILE+Logger.TERMINAL,"checkPing runs");
			DateTime now = DateTime.Now;
			for(int i=0; i<=this.sm.sessions.Count-1; i++)
			{
				Session s = (Session)this.sm.sessions[i];
				if ((s.lastPing.AddMinutes(int.Parse((string)this.c.values["logout_time_without_ping"]))).CompareTo(now) <= 0)
				{
					// ping + 5 minuten ist kleiner als jetzt
					// => ping ist länger als 5 minuten her
					// session de.value (session-key) muss sauber ausgeloggt werden
					this.l.writeDebug(Logger.FILE+Logger.TERMINAL, s.com.clientIP.ToString() +" - No Ping - Logout!");
					this.logout(s);
				}
			}
		}
		
		public void logout(Session s)
		{
			s.logout();
			this.sm.removeSession(s);
			s = null;
		}
		
		public void login(Telegram t, Session s)
		{
			Error e = null;
			String username = "";
			String password = "";
			if(t.countParameters() != 2)
			{
				e = new Error(1000);
			}
			else
			{
				username = t[0];
				password = t[1];
			}
			
			if(username.Equals(String.Empty))
			{
				e = new Error(1001);
			}
			if(password.Equals(String.Empty))
			{
				e = new Error(1002);
			}
		
			User u = new User();
			if(e == null)
			{
				// No errors
				u = this.db.getUser(username, password, DateTime.Now.Ticks);
				if(u.IsLoggedIn)
				{
					s.User = u;
					s.com.sendLoginOk(t.key);
					this.db.setUserWrongPassword(s.User, 0);
				}
				else
				{
					e = u.e;
					if(e.code == 1003)
					{
						s.User = this.db.setUserWrongPassword(s.User, s.User.countWrongPassword+1);
						if(s.User.countWrongPassword > int.Parse((string)this.c.values["max_allowed_wrong_password"]))
						{
							// To many wrong Passwords -> Bann Player for 30 seconds to pretend of Brute Force Attacks
							this.db.setUserBannToTime(s.User, DateTime.Now.Ticks + long.Parse((string)this.c.values["ban_time_at_wrong_password"]));
						}
						
					}
				}
			}
		
			if(e != null)
			{
				this.l.writeError(Logger.FILE+Logger.TERMINAL, this.error.merge(e));
				/*
				Telegram ttt = new Telegram("LOGIN", Telegram.RESPONSE, this.dm.currentTelegramKey);
				ttt.addParameter(t.key);
				ttt.addParameter("NOK");
				ttt.addParameter(((Error)this.error.merge(e)).toString());
				*/
				Telegram ttt =  Telegram.getResponse(this.dm.currentTelegramKey, t.key, "NOK", ((Error)this.error.merge(e)).toString());
				s.com.sendTelegram(ttt);
			}
		}
		
		public void adminThread()
		{
			
			TcpListener adminListener = new TcpListener(IPAddress.Parse((string)this.c.values["admin_ip"]),int.Parse((string)this.c.values["admin_port"]));
			adminListener.Start();
			
			while(true)
			{
				TcpClient client = adminListener.AcceptTcpClient();
				CommunicatorParallel c = new CommunicatorParallel(client, this.dm, this.l);
				//c.dm = this.dm;
				//String firstTelegram = c.readFirstTelegram();
				//String[] args = firstTelegram.Split(';');
				
				Session s = new Session("adminthread");
				while(c.threadStarted)
				{
					if(c.requestQueue.Count > 0)
					{
						Telegram t = (Telegram)c.requestQueue.Dequeue();
						switch(t.keyWord)
						{
						case "LOGIN":
							try
							{
								String name = t[0];
								String pw = t[1];
								User admin = this.db.getAdmin(name, pw);
								if(admin.IsLoggedIn)
								{
									s.User = admin;
									c.sendLoginOk(t.key);
									c.sendGreeting("Willkommen auf der NCC-1701 :)\n___________________          _-_\n\\==============_=_/ ____.---'---`---.____\n             \\_ \\    \\----._________.----/\n               \\ \\   /  /    `-_-'\n           __,--`.`-'..'-_\n          /____          ||\n               `--.____,-'");
								} else {
									c.sendLoginNok(t.key, ((Error)this.error.merge(admin.e)).toString());
								}
							} catch(Exception e) {
								this.l.writeError(Logger.FILE+Logger.TERMINAL, "**** Admin-Error @ LOGIN **** " + e.Message);
								c.closeConnection();
							}
							break;
							
						/*	
						case "GETSESSIONS":
							try
							{
								if(s.Player.IsLoggedIn && s.Player.getAdminLevel() >= (int)this.c.adminPermissions["GETSESSIONS"])
								{
									Hashtable st = this.sessionTable;
									if(st.Count > 0)
									{
										foreach(DictionaryEntry de in st)
										{
											c.sendSessionData((Session)de.Value);
										}
									} else {
										c.sendEmptySessionData();
									}
								} else {
									if(!s.Player.IsLoggedIn)
									{
										throw(new Exception("Admin not logged in"));
									}
									if(s.Player.getAdminLevel() < (int)this.c.adminPermissions["GETSESSIONS"])
									{
										throw(new Exception("Admin doesn't have permission"));
									}
								}
							} catch(Exception e) {
								this.l.writeError(Logger.FILE+Logger.TERMINAL, "**** Admin-Error @ GETSESSIONS **** " + e.Message);
							}
							break;
							
						case "LOGOUTUSER":
							try
							{
								if(s.Player.IsLoggedIn && s.Player.getAdminLevel() >= (int)this.c.adminPermissions["LOGOUTUSER"])
								{
									String sessionkey = args[1];
									this.logout((Session)this.sessionTable[sessionkey]);
								} else {
									if(!s.Player.IsLoggedIn)
									{
										throw(new Exception("Admin not logged in"));
									}
									if(s.Player.getAdminLevel() < (int)this.c.adminPermissions["LOGOUTUSER"])
									{
										throw(new Exception("Admin doesn't have permission"));
									}
								}
							} catch(Exception e) {
								this.l.writeError(Logger.FILE+Logger.TERMINAL, "**** Admin-Error @ LOGOUTUSER **** " + e.Message);
							}
							break;
						
						case "SETBANNTIMEFROMSESSIONKEY":
							try
							{
								if(s.Player.IsLoggedIn && s.Player.getAdminLevel() >= (int)this.c.adminPermissions["SETBANNTIMEFROMSESSIONKEY"])
								{
									String sessionkey = args[1];
									int banntime = Int32.Parse(args[2]);
									User u = ((Session)this.sessionTable[sessionkey]).Player;
									this.db.setUserBannToTime(u, banntime);
									
								} else {
									if(!s.Player.IsLoggedIn)
									{
										throw(new Exception("Admin not logged in"));
									}
									if(s.Player.getAdminLevel() < (int)this.c.adminPermissions["SETBANNTIMEFROMSESSIONKEY"])
									{
										throw(new Exception("Admin doesn't have permission"));
									}
								}
							} catch(Exception e) {
								this.l.writeError(Logger.FILE+Logger.TERMINAL, "**** Admin-Error @ SETBANNTIMEFROMSESSIONKEY **** " + e.Message);
							}
							break;
							
						
						case "SENDTELEGRAMTOSESSION":
							try
							{
								if(s.Player.IsLoggedIn && s.Player.getAdminLevel() >= (int)this.c.adminPermissions["SENDTELEGRAMTOSESSION"])
								{
									String sessionkey = args[1];
									s = (Session)this.sessionTable[sessionkey];
									String telegram = "";
									for(int i=2;i<=args.Length-1;i++) {
										telegram = telegram + args[i] + ";";
									}
									telegram = telegram.Remove(telegram.Length-1);
									s.com.sendTelegram(telegram);
								} else {
									if(!s.Player.IsLoggedIn)
									{
										throw(new Exception("Admin not logged in"));
									}
									if(s.Player.getAdminLevel() < (int)this.c.adminPermissions["SENDTELEGRAMTOSESSION"])
									{
										throw(new Exception("Admin doesn't have permission"));
									}
								}
							} catch(Exception e) {
								this.l.writeError(Logger.FILE+Logger.TERMINAL, "**** Admin-Error @ SENDTELEGRAMTOSESSION **** " + e.Message);
							}
							break;
						*/
						case "LOGOUT":
							try
							{
								s = null;
								c.sendLogout();
								c.closeConnection();
							} catch(Exception e) {
								//throw(new Exception("**** Admin-Error @ LOGOUT **** " + e.Message));
								this.l.writeError(Logger.FILE+Logger.TERMINAL, "**** Admin-Error @ LOGOUT **** " + e.Message);
							}
							break;
							
						case "STOPSERVER":
							this.l.writeInfo(Logger.FILE+Logger.TERMINAL, "Received stopserver signal...");
							this.tm.stopThread(this.mainThreadID);
							//Telegram tt = new Telegram("TERM", Telegram.REQUEST, this.dm.currentTelegramKey);
							Telegram tt = Telegram.getRequest(this.dm.currentTelegramKey, "TERM");
							this.l.writeInfo(Logger.FILE+Logger.TERMINAL, "Stopping all Crons...");
							this.cron.stopAllJobs();
							this.l.writeInfo(Logger.FILE+Logger.TERMINAL, "Sending all Clients TERM-Signal...");
							this.sm.broadcast(tt);
							this.l.writeInfo(Logger.FILE+Logger.TERMINAL, "Starting Cron-Job to force logout...");
							this.cron.addJob("killclientconnections", new ElapsedEventHandler(this.killClientConnections), 60);
							this.cron.startJob("killclientconnections");
							c.closeConnection();
							this.tm.stopThread(this.adminThreadID);
							break;
						}
						
					}
					
					if(c.responseQueue.Count > 0)
					{
						// Empfangene Responses verarbeiten
						Telegram t = (Telegram)c.responseQueue.Dequeue();
						switch(t.keyWord)
						{
							// Es gibt noch keine Responses für den Server im Telegrammprotokoll
						}
					}
				}
			}
			
		}
		
		public void killClientConnections(object sender, ElapsedEventArgs e)
		{
			this.l.writeWarning(Logger.FILE+Logger.TERMINAL, "Force Logout clients...");
			this.sm.logoutAll();
			this.cron.stopJob("killclientconnections");
		}
		
		public void clientThread()
		{
			this.l.writeDebug(Logger.FILE+Logger.TERMINAL,"Client-Thread started!");
			Session s = (Session)this.sessionQueue.Dequeue();
			this.sm.addSession(s);
			
			
			TableConnector tc = new TableConnector(s.com, this.dm.currentConnectorKey);
			//this.testtable.connector = tc;
			s.com.addConnector(tc);
						
			while(s.clientThreadStarted)
			{
				if(s.com.requestQueue.Count > 0)
				{
					/*
					Telegram t = null;
					if(s.com.requestQueue.Count > 0)
						t = (Telegram)s.com.requestQueue.Dequeue();
					*/
					Telegram t = (Telegram)s.com.requestQueue.Dequeue();
				
					switch(t.keyWord)
					{
					case "LOGIN":
						this.login(t, s);
						break;
							
					case "LOGOUT":
						// TODO logout nur ausführen wenn user eingeloggt ist
					
						this.l.writeInfo(Logger.FILE+Logger.TERMINAL, "Logout Client: " + s.com.clientIP.ToString() + " - Key: " + s.key);
						this.logout(s);
						break;
							
					case "PING":
						this.l.writeDebug(Logger.FILE+Logger.TERMINAL, "Ping von Client: " + s.com.clientIP.ToString() + " - Key: " + s.key);
						s.com.sendPingResponse(t.key);
						s.lastPing = DateTime.Now;
						this.testtable.addRow("hutzi", "putzi", "lala");
						break;
						
					case "TELEGRAMERROR":
						this.l.writeDebug(Logger.FILE+Logger.TERMINAL, s.com.clientIP.ToString() + " - Fehlerhaftes Telegram:" + t[0]);
						break;
					/*	
					case "CHATMESSAGE":
						if(s.Player.IsLoggedIn)
						{
							int time = this.dm.getTimeStamp();
							this.l.writeDebug(Logger.FILE+Logger.TERMINAL, "Message from Client: " + s.key + " --- " + t[0]);
							//this.chat.addMessage(s.Player, time, s.com.clientIP, s.key, t[0]);
							this.connectionManager.broadcast(t[0], s, time);
						} else {
							this.l.writeWarning(Logger.FILE+Logger.TERMINAL, "Unauthorizied Chatmessage - Key: " + s.key + " IP: " + s.com.clientIP.ToString());
						}
						break;
					*/	
					default:
						this.l.writeDebug(Logger.FILE+Logger.TERMINAL, s.com.clientIP.ToString() + " - Fehlerhaftes Kommando:" + t.keyWord);
						break;
					}
				}
				
				if(s.com.responseQueue.Count > 0)
				{
					// Empfangene Responses verarbeiten
					Telegram t = (Telegram)s.com.responseQueue.Dequeue();
					switch(t.keyWord)
					{
						// Es gibt noch keine Responses für den Server im Telegrammprotokoll
					}
				}
				
				if(s.com.connectorQueue.Count > 0)
				{
					// Empfangene Connector Telegramme verarbeiten
					Telegram t = (Telegram)s.com.connectorQueue.Dequeue();
					switch(t.keyWord)
					{
					case "BUGTABLE":
						
						break;
					}
				}
			}
		}
		
	}
}
